import pyglet
import pymunk
import math
import engine.resource

class GameObject(object):
    """
    Common base class for all game objects in the game.
    """
    
    def __init__(self, *args, **kwargs):
        pass
    
    def on_added(self):
        """
        Stub message handler for 'on_added' messages.
        """
        pass
    
    def on_removed(self):
        """
        Stub message handler for 'on_removed' messages.
        """
        pass
        
    def update(self, dt):
        """
        This is a good spot to insert AI and Input gathering.
        """
        pass
        
    def on_key_press(self, key, modifiers):
        """
        Default key event handling function.
        """
        pass
        
    def on_key_release(self, key, modifiers):
        """
        Default key event handling function.
        """
        pass
        
class GraphicalObject(GameObject):
    """
    Abstract class for all visual objects.
    """
    
    image_path = None           # path to the static image.
    animation_path = None       # path to the animation image
    animation_tiling = (1, 1)   # the tiling of the animation image
    animation_duration = 1.     # the overall duration of the animation TODO: not yet working
    scale = 1.                  # the size-scale of the object
    
    def __init__(self, *args, **kwargs):
        """
        Initializes a GraphicalObject with either a static image or
        an animation.
        """
        self.image_path = kwargs.get('image_path', self.image_path)
        self.animation_path = kwargs.get('animation_path', self.animation_path)
        scale = kwargs.get('scale', self.scale)
        
        self.scale = scale
        
        if self.image_path is not None:
            image = engine.resource.image(self.image_path)
            image.anchor_x = image.width / 2
            image.anchor_y = image.height / 2
            self.sprite = pyglet.sprite.Sprite(image)
            self.sprite.position = kwargs.get('position', (0, 0))        
        
        elif self.animation_path is not None:
            seq = pyglet.image.ImageGrid(engine.resource.image(self.animation_path), 
                                         *self.animation_tiling)
            
            # TODO overall/frame duration not working
            frame_duration = self.animation_duration / (self.animation_tiling[0] *
                                                        self.animation_tiling[1])
            animation = pyglet.image.Animation.from_image_sequence(seq,
                                                                   frame_duration)
            self.bin = pyglet.image.atlas.TextureBin()
            
            #TODO: not working...
            #animation.add_to_texture_bin(bin)
            
            self.sprite = pyglet.sprite.Sprite(animation)
            self.sprite.position = kwargs.get('position', (0, 0))
            self.sprite.x -= animation.get_max_width() * scale / 2
            self.sprite.y -= animation.get_max_height() * scale / 2
            
            @self.sprite.event
            def on_animation_end():
                self.on_animation_end()
        else:
            raise Exception("No image path specified.")
        
        self.sprite.rotation = math.degrees(kwargs.get('angle', 0))
        self.sprite.scale = scale
    
    def on_animation_end(self):
        """
        Default event handler for ending animations.
        """
        pass
        
class PhysicalObject(GameObject):
    """
    Abstract subclass for all GameObjects with physical interaction.
    """
    maximum_speed = 200.        # default maximum speed
    scale = 1.                  # the size-scale of the object
    mass = 1.                   # default mass
    radius = None               # radius for circles
    points = None               # points for polygons
    
    group = 0                   # collision group
    layers = -1                 # collision layers
    sensor = False              # sensor flag
    elasticity = 1.             # bouncing property
    friction = 1.               # friction property
    
    def __init__(self, *args, **kwargs):
        """
        Initializes a PhysicalObject with either a Circle or a Polygon shape.
        """
        scale = kwargs.get('scale', self.scale)
        mass = kwargs.get('mass', self.mass) * scale
        radius = kwargs.get('radius', self.radius)
        points = kwargs.get('points', self.points)
        
        self.scale = scale
        
        if radius is not None:
            radius *= scale
            moment = pymunk.moment_for_circle(mass, 0, radius)
            self.body = pymunk.Body(mass, moment)
            self.shape = pymunk.Circle(self.body, radius)
        
        elif points is not None:
            vertices = map(pymunk.Vec2d, self.points)
            for vertex in vertices:
                vertex *= scale
            moment = pymunk.moment_for_poly(mass, vertices)
            self.body = pymunk.Body(mass, moment)
            self.shape = pymunk.Poly(self.body, vertices)
        else:
            raise Exception("Neither radius nor points are specified")
        
        self.body._bodycontents.v_limit = self.maximum_speed
        
        # set up hook to get from the body to the game object
        self.body.object = self
        
        # set up initial parameters
        self.body.position = kwargs.get('position', (0, 0))
        self.body.velocity = kwargs.get('velocity', (0, 0))
        self.body.angle = kwargs.get('angle', 0)
        
        self.shape.group = kwargs.get('group', self.group)
        self.shape.layers = kwargs.get('layers', self.layers)
        self.shape.sensor = kwargs.get('sensor', self.sensor)
        self.shape.elasticity = kwargs.get('elasticity', self.elasticity)
        self.shape.friction = kwargs.get('friction', self.friction)
        
    def on_collision(self, other, arbiter):
        """
        Default collision handling function. Return True if
        collision shall be accepted, e.e processed further, or
        False if not.
        """
        return True
        
class CombinedObject(GraphicalObject, PhysicalObject):
    """
    Convenience class for combined graphical and physical objects.
    """
    
    def __init__(self, *args, **kwargs):
        """
        Convenience CTOR to initialize both Graphical and Physical part
        of the object.
        """
        GraphicalObject.__init__(self, *args, **kwargs)
        PhysicalObject.__init__(self, *args, **kwargs)
